#pragma once
#include <sys/wait.h>
#include <pthread.h>
#include "Common.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

const int default_fd = -1;
const int default_backlog = 6;

using namespace myLog;
using namespace myThreadPool;

using Tcp_func_t = std::function<std::string(std::string, const InetAddr&)>;
using task_t = std::function<void()>;

//通过继承无法拷贝的父类 -> 子类也没办法拷贝！
class TcpServer : public NoCopy{
public:
    TcpServer(uint16_t port, Tcp_func_t func)
        :_is_running(false),
        _port(port), 
        _listen_fd(default_fd),
        _func(func)
    {}

    ~TcpServer(){}

    void Init(){
    //1.创建套接字
        _listen_fd = socket(AF_INET, SOCK_STREAM, 0); //和UDP不同的是，数据变为流式SOCK_STREAM
        if(_listen_fd < 0){
            LOG(LogLevel::FATAL) << "Init : socket(listen_fd) error";
            exit(SOCKET_ERROR);
        }
        LOG(LogLevel::INFO) << "socket create success, _listen_fd : " << _listen_fd;
        //这里的套接字其实是为了后序listen接口准备的，这里和UDP不太一样

    //2.服务器显示绑定套接字文件
        InetAddr local(std::to_string(INADDR_ANY), _port);
        int bind_id = bind(_listen_fd, local.GetSockAddr_Ptr(), local.GetAddrSize());
        if(bind_id < 0){
            LOG(LogLevel::FATAL) << "bind to socket(listen_fd) error";
            exit(BIND_ERROR);
        }
        LOG(LogLevel::INFO) << "bind to socket(listen_fd) success, _listen_fd : " << _listen_fd;

    //3.服务器端需要进行监听——listen(其实就是等待连接)
    //只要服务器处于listen状态，就是可以被连接的！
        //listen的第二个参数是表明监听的时候，单次可以连接的最大数量，一般不能太高，也不能太低
        int listen_id = listen(_listen_fd, default_backlog);
        if(listen_id < 0){
            LOG(LogLevel::FATAL) << "lieten error";
            exit(LISTEN_ERROR);
        }
        LOG(LogLevel::INFO) << "listen success, _listen_fd : " << _listen_fd;
    }

    void Service(int sockfd, const InetAddr& addr){
        LOG(LogLevel::DEBUG) << addr.GetFormatStr() << "enter in service";
        char readbuffer[1024] = {0};
        while(1){
            memset(readbuffer, 0, sizeof(readbuffer));
            //a.因为是字节流形式，所以读消息其实是通过对应的文件描述符来使用read
            ssize_t read_id = read(sockfd, readbuffer, sizeof(readbuffer) - 1);
            if(read_id < 0){
                LOG(LogLevel::WARNING) << "read form" << addr.GetFormatStr() << "error";
                close(sockfd);
                break;
            }
            else if(read_id == 0){
                LOG(LogLevel::INFO) << addr.GetFormatStr() << "exit from service";
                close(sockfd);
                break;
            }
            else{
                std::string ret =  addr.GetFormatStr() + "# " + readbuffer;
                //发送回去 -> write to sockfd
                ssize_t write_id = write(sockfd, ret.c_str(), ret.size());
                LOG(LogLevel::DEBUG) << "write" << ret << " to " << addr.GetFormatStr();
            }
        }
    }

    class ThreadData{
    public:
        ThreadData(TcpServer* server, int sockfd, const InetAddr& addr, pthread_t tid)
            :_server(server), _sockfd(sockfd), _addr(addr), _tid(tid)
        {}
        ~ThreadData(){}

        TcpServer* _server;
        int _sockfd;
        const InetAddr& _addr;
        pthread_t _tid;
    };

    /*static void* Routine(void* args){
        ThreadData* tp = static_cast<ThreadData*>(args);
        pthread_detach(tp->_tid);
        tp->_server->Service(tp->_sockfd, tp->_addr);
        return nullptr;
    } */


    static void* Routine_Translate(void* args){
        ThreadData* tp = static_cast<ThreadData*>(args);
        pthread_detach(tp->_tid);
        while(1){
            //1.读单词
            char buffer[32] = {0};
            ssize_t n = read(tp->_sockfd, buffer, sizeof(buffer) - 1);
            if(n < 0){
                LOG(LogLevel::WARNING) << "read form" << tp->_addr.GetFormatStr() << "error";
                close(tp->_sockfd);
                break;
            }
            else if(n == 0){
                LOG(LogLevel::INFO) << tp->_addr.GetFormatStr() << "exit from service";
                close(tp->_sockfd);
                break;
            }
            else{
                std::string chinese = tp->_server->_func(buffer, tp->_addr);
                write(tp->_sockfd, chinese.c_str(), chinese.size());
            }

        }
        return nullptr;
    }

    //处理命令的相关函数其实已经写过了，就是myshell，但是这里想用一个新的接口！
    static void* Routine_Execute(void* args){
        ThreadData* tp = static_cast<ThreadData*>(args);
        pthread_detach(tp->_tid);
        while(1){
            //1.读命令 使用
            char cmd[32] = {0};
            ssize_t n = read(tp->_sockfd, cmd, sizeof(cmd) - 1);
            if(n < 0){
                LOG(LogLevel::WARNING) << "read form" << tp->_addr.GetFormatStr() << "error";
                close(tp->_sockfd);
                break;
            }
            else if(n == 0){
                LOG(LogLevel::INFO) << tp->_addr.GetFormatStr() << "exit from service";
                close(tp->_sockfd);
                break;
            }
            else{
                std::string res = tp->_server->_func(cmd, tp->_addr);
                write(tp->_sockfd, res.c_str(), res.size());
            }
        }
        return nullptr;
    }

    void Run(){
        _is_running = true;
        while(_is_running){
        //使用TCP进行收发消息前，是需要先获取连接的！ —— accept
            struct sockaddr_in _peer;
            socklen_t len = sizeof(sockaddr_in);
            //如果没有连接到，是会阻塞的！
            int sockfd = accept(_listen_fd, CONVERT(&_peer), &len);
            InetAddr peer(_peer);
            if(sockfd < 0){
                LOG(LogLevel::WARNING) << "accept error";
                exit(ACCEPT_ERROR);
            }
            LOG(LogLevel::INFO) << "accept" << peer.GetFormatStr() << "success";

            //其实获取连接后，会返回一个文件描述符！ 这个和一开始socket接口创建的是不一样的！
            //前面的好比是打开一个文件描述符，用于不断的监听
            //后面是重新分配一个新的，用于IO操作
            
            //-----------------------------------------长业务-------------------------------------------------//
            //不管func的使用  -> 直接先耦合测试一下

            //1.先写一个单进程版本 
            //Service(sockfd, peer);
            //但是，这样写一次只能使用一个进程 -> 每个服务都是长服务

            //2.使用多进程 -> 使用原生库写了
            /* pid_t id = fork();
            if(id < 0){
                LOG(LogLevel::FATAL) << "fork error";
                exit(FORK_ERROR);
            }
            else if(id == 0){
                //使用多进程的情况来说：子进程也是看得到父进程创建的_listen_fd的，我们不希望它能够看见，应该关掉
                close(_listen_fd);
                pid_t g_id = fork();
                
                if(g_id > 0) exit(OK);// 局部父进程，其实就是所说的"子进程"
                Service(sockfd, peer);
                     
            }
            else{
                //父进程而言，因为让子进程去使用sockfd去进行Service了(子进程已经从父进程那里拷贝了，所以，父进程直接关掉即可)
                //其实不管也没所谓，但是就怕出现一些未定义的错误！而且，从标准设计上来讲，应当关掉
                close(sockfd);
                waitpid(id, nullptr, 0);
                //这里会进行阻塞等待，这是不行的！要不然还是串行服务。
                //非阻塞式轮询等待也不行，因为前面accept的时候也可能会阻塞，可能一次没等到，下次被accept卡住了
                //两种做法
                    //a.直接捕捉信号，即signal(SIGCHLD, nullptr); -> 忽略该信号
                    //b.还有一种方法就是，让子进程再创建子进程！即父进程等待子进程，孙子进程去完成服务。
                    //但是，直接让子进程退出！ -> 父进程立马回收了，孙子进程变为孤儿进程，1号进程回收！(√)
            } */
            

            //3.使用多线程
            /* pthread_t tid;
        
            ThreadData* tp = new ThreadData(this, sockfd, peer, tid);
            pthread_create(&tid, nullptr, Routine, tp);
            //创建出来的新线程不能把sockfd关了，因为线程是共享的
            pthread_join(tid, nullptr); */
            //但是，这里也是会阻塞回收的，所以手动设置分离即可！
            
            //-----------------------------------------短业务-----------------------------------------------//

            //4.使用线程池 + func(这个服务，外界提供！)
            //前面都是长服务，但是，有时候也需要短服务(只有向服务器请求的时候才进行服务，其余时间不会服务)
            //线程池比较合适短服务，但是今天这里accept会卡住！
            
           /*  auto pool = ThreadPool<task_t>::GetInstance();
            char message[1024] = {0};
            ssize_t n = read(sockfd, message, sizeof(message) - 1);
            (void)n;
            
            task_t t = std::bind(_func, message, peer);
            pool->Enqueue(t);

            //翻译
            std::string ret = _func(message, peer);
            //2.返回给客户端
            n = write(sockfd, ret.c_str(), ret.size());
            (void)n;
            close(sockfd); */


            
            //-----------------------------------------外界业务-----------------------------------------------//
            //这里我们就使用多线程来进行翻译单词！
            /* pthread_t tid;
            ThreadData* tp = new ThreadData(this, sockfd, peer, tid);
            pthread_create(&tid, nullptr, Routine_Translate, tp);
            pthread_join(tid, nullptr); */


            //外界传输命令(ls -a -l -n)，然后这里做处理，再把结果转发回给客户端
            pthread_t tid;
            ThreadData* tp = new ThreadData(this, sockfd, peer, tid);
            pthread_create(&tid, nullptr, Routine_Execute, tp); 
            pthread_join(tid, nullptr);
        }
    }

private:
    bool _is_running;
    uint16_t _port; 
    int _listen_fd;
    Tcp_func_t _func;
};
